﻿namespace SPDeploy.Library.Processing.Processors
{
    using System;
    using Data;
    using Exceptions;
    using Helpers;
    using Library;
    using Microsoft.SharePoint.Administration;
    using Action=SPDeploy.Data.Action;

    public class WebApplicationProcessor : ProcessorBase<WebApplication, SPWebApplication>
    {
        #region Properties: public

        [ProcessorSetting(DefaultValue = IgnorableBehaviors.Ignore)]
        public IgnorableBehaviors AlreadyDeleted { get; set; }

        [ProcessorSetting(DefaultValue = true)]
        public bool DeleteContentDatabases { get; set; }

        [ProcessorSetting(DefaultValue = true)]
        public bool DeleteWebSiteFromIis { get; set; }

        public override string Name
        {
            get { return "WebApplication"; }
        }

        #endregion

        #region Methods: public

        public override SPWebApplication Install(ProcessorContext ctx, WebApplication config)
        {
            Log(LogSeverity.Step, "{0} '{1}'", Describe(ProcessorModes.Install, config.Action), config.Name);
            SPFarm farm = GetFarm();
            SPWebService service = GetServices(farm);
            ValidateConfigurationContent(config);

            if (config.Action == Action.Lookup)
            {
                return FindExistingWebApplicationOrThrow(farm, service, config);
            }

            if (config.Action == Action.LookupOrCreate)
            {
                SPWebApplication webApp = FindExistingWebApplication(service, config);
                return webApp ?? CreateWebApplication(ctx, config, farm);
            }

            return CreateWebApplication(ctx, config, farm);
        }

        public override SPWebApplication Uninstall(ProcessorContext ctx, WebApplication config)
        {
            Log(LogSeverity.Step, "{0} '{1}'", Describe(ProcessorModes.Uninstall, config.Action), config.Name);
            SPFarm farm = GetFarm();
            SPWebService service = GetServices(farm);
            ValidateConfigurationContent(config);

            if (config.Action == Action.Create)
            {
                SPWebApplication webApp = FindExistingWebApplication(service, config);
                if (webApp != null)
                {
                    DeleteWebApplication(farm, webApp, config);
                }
                else if (AlreadyDeleted == IgnorableBehaviors.Error)
                {
                    throw new DeploymentTargetProcessorException(string.Format("Web application could not be found with given guid ({0}) or name ({1})", config.Guid, config.Name));
                }
                ctx.ChildrenDeleted = true;
                return null;
            }

            return FindExistingWebApplicationOrThrow(farm, service, config);
        }

        public override SPWebApplication Update(ProcessorContext ctx, WebApplication oldConfig, WebApplication newConfig)
        {
            throw new NotImplementedException();
        }

        public void ValidateConfigurationContent(WebApplication config)
        {
            if (config.Guid.IsEmpty() && config.Name.IsEmpty())
            {
                throw new DeploymentTargetConfigException("Guid and Name cannot both be empty for WebApplication config nodes");
            }
        }

        #endregion

        #region Methods: private

        private static void ConfigureApplicationPoolSettings(WebApplication config, SPWebApplicationBuilder appBuilder)
        {
            if (config.CreatePool &&
                !string.IsNullOrEmpty(appBuilder.ApplicationPoolUsername))
            {
                appBuilder.IdentityType = IdentityType.SpecificUser;
                appBuilder.ApplicationPoolUsername = config.PoolUser;
                foreach (char c in config.PoolPassword)
                {
                    appBuilder.ApplicationPoolPassword.AppendChar(c);
                }
            }
            else
            {
                appBuilder.IdentityType = IdentityType.NetworkService;
            }
        }

        private static void ConfigureContentDatabaseSettings(WebApplication config, SPWebApplicationBuilder appBuilder)
        {
            if (!string.IsNullOrEmpty(appBuilder.DatabaseUsername))
            {
                appBuilder.DatabaseUsername = config.DatabaseUser;
                appBuilder.DatabasePassword = config.DatabasePassword;
            }
            else
            {
                appBuilder.DatabaseUsername = null;
            }
        }

        private SPWebApplication CreateWebApplication(ProcessorContext ctx, WebApplication config, SPFarm farm)
        {
            if (config.Name.IsEmpty() ||
                config.Database.IsEmpty() ||
                config.PoolName.IsEmpty())
            {
                throw new DeploymentTargetConfigException("The fields Name, Database and PoolName must all be defined for WebApplication config nodes");
            }

            var appBuilder = new SPWebApplicationBuilder(farm)
                                 {
                                     Port = config.Port,
                                     HostHeader = config.Header,
                                     ApplicationPoolId = config.PoolName,
                                     AllowAnonymousAccess = config.AllowAnonymous,
                                     UseSecureSocketsLayer = config.UseSSL,
                                     CreateNewDatabase = true,
                                     DatabaseName = config.Database
                                 };

            ConfigureApplicationPoolSettings(config, appBuilder);
            ConfigureContentDatabaseSettings(config, appBuilder);

            SPWebApplication newWebApp = appBuilder.Create();
            newWebApp.Name = config.Name;
            newWebApp.Update();
            newWebApp.Provision();

            config.Guid = newWebApp.Id.ToString();

            return newWebApp;
        }

        private static void DeleteApplicationPool(SPWebApplication webApp)
        {
            webApp.ApplicationPool.Unprovision();
        }

        private static void DeleteDatabases(SPWebApplication webApp)
        {
            foreach (SPContentDatabase db in webApp.ContentDatabases)
            {
                db.Unprovision();
            }
        }

        private void DeleteWebApplication(SPFarm farm, SPWebApplication webApp, WebApplication config)
        {
            if (DeleteContentDatabases)
            {
                DeleteDatabases(webApp);
            }

            AdminServiceTools.PreformIisCleanup(webApp, config.CreatePool && DeleteWebSiteFromIis, webApp.IisSettings.Values);

            if (config.CreatePool && DeleteWebSiteFromIis)
            {
                DeleteApplicationPool(webApp);
            }

            webApp.Delete();

            farm.Unprovision();
        }

        private static SPWebApplication FindExistingWebApplication(SPWebService service, WebApplication config)
        {
            SPWebApplication webApp = null;
            if (!config.Guid.IsEmpty())
            {
                webApp = service.WebApplications[new Guid(config.Guid)];
            }
            else
            {
                webApp = service.WebApplications[config.Name];
            }
            return webApp;
        }

        private static SPWebApplication FindExistingWebApplicationOrThrow(SPFarm farm, SPWebService service, WebApplication config)
        {
            SPWebApplication webApp = FindExistingWebApplication(service, config);
            if (webApp == null)
            {
                throw new DeploymentTargetProcessorException(string.Format("Web application could not be found with given guid ({0}) or name ({1})", config.Guid, config.Name));
            }
            return webApp;
        }

        private static SPFarm GetFarm()
        {
            SPFarm farm = SPFarm.Local;
            if (farm == null)
            {
                throw new DeploymentTargetProcessorException("No local farm found");
            }
            return farm;
        }

        private SPWebService GetServices(SPFarm farm)
        {
            var service = farm.Services.GetValue<SPWebService>("");
            if (service == null)
            {
                throw new DeploymentTargetProcessorException("Farm services could not be reached");
            }
            return service;
        }

        #endregion
    }
}